using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;

namespace Oops.Xna.Framework.Content.Pipeline
{
	[ContentImporter(".xna_mesh", DefaultProcessor = "Mesh - Oops! XNA Framework", DisplayName = "Oops! XNA Mesh File")]
    public class XnaMeshImporter : ContentImporter<NodeContent>
	{
		private XmlDocument document;
        private NodeContent rootContent;

		private Dictionary<int, int> indices = new Dictionary<int, int>();
		private Dictionary<int, XmlElement> vertexXmlElementsById = new Dictionary<int, XmlElement>();
		
		private Dictionary<int, NodeContent> bones = new Dictionary<int, NodeContent>();
		private Dictionary<int, string> diffuseTextures = new Dictionary<int, string>();
		private Dictionary<int, MaterialContent> materials = new Dictionary<int, MaterialContent>();

        public override NodeContent Import(String filename, ContentImporterContext context)
		{
			//System.Diagnostics.Debugger.Launch();

			// The context parameter must be specified.
			if (context == null)
			{
				// TODO :
				throw new PipelineException("");
			}

			// Load the file into the xml document.
			this.document = new XmlDocument();
			this.document.Load(filename);
			
			// Create the root content instance.
            this.rootContent = new NodeContent();

			// Import the sub-content.
			this.ImportMaterials(context);
			this.ImportMeshes(context);

			// Return the model content.
			return this.rootContent;
		}

		private void ImportMaterials(ContentImporterContext context)
		{
			// Assert parameters.
			Debug.Assert(context != null);

			// Iterate on found material definitions.
			this.materials.Clear();
			foreach (XmlNode materialXml in this.document.SelectNodes(".//material"))
			{
				// Get the id of the current material.
				int id = XmlConvert.ToInt32(materialXml.Attributes["id"].Value);

				// Get the diffuse texture id of the current material.
				// TODO : int diffuseTextureId = XmlConvert.ToInt32(materialXml.Attributes["diffuseTextureId"].Value);
				/*
				// Get the diffuse color of the current material.
				Vector3 diffuseColor;
				diffuseColor.X = XmlConvert.ToSingle(materialXml["diffuse"].Attributes["red"].Value);
				diffuseColor.Y = XmlConvert.ToSingle(materialXml["diffuse"].Attributes["green"].Value);
				diffuseColor.Z = XmlConvert.ToSingle(materialXml["diffuse"].Attributes["blue"].Value);

				// Get the specular color of the current material.
				Vector3 specularColor;
				specularColor.X = XmlConvert.ToSingle(materialXml["specular"].Attributes["red"].Value);
				specularColor.Y = XmlConvert.ToSingle(materialXml["specular"].Attributes["green"].Value);
				specularColor.Z = XmlConvert.ToSingle(materialXml["specular"].Attributes["blue"].Value);
				float specularPower = XmlConvert.ToSingle(materialXml["specular"].Attributes["intensity"].Value);
				 */
				// Create a new material content from the information in the current iteration.
				//XnamlMaterialContent materialContent = new XnamlMaterialContent();
				BasicMaterialContent materialContent = new BasicMaterialContent();
				materialContent.DiffuseColor = Color.Yellow.ToVector3();// diffuseColor;
				materialContent.SpecularColor = null;// specularColor;
				materialContent.SpecularPower = null;// specularPower;
				materialContent.VertexColorEnabled = true;

				//materialContent.Texture = new ExternalReference<Texture2DContent>(filename);
				//materialContent.Effect = new ExternalReference<EffectContent>(@".\Content\Effects\XnamlEffect.fx");
				
				// Add the material to private list.
				this.materials.Add(id, materialContent);
			}
		}

		private void ImportMeshes(ContentImporterContext context)
		{
			// Assert parameters.
			Debug.Assert(context != null);

			// Iterate on found mesh definitions.
			foreach (XmlNode meshXml in this.document.SelectNodes(".//mesh"))
			{
				// Get the id of the current mesh.
				int id = XmlConvert.ToInt32(meshXml.Attributes["id"].Value);

				// Get the material id of the current mesh.
				int materialId = XmlConvert.ToInt32(meshXml.Attributes["materialId"].Value);

				// Start building the mesh.
				MeshBuilder meshBuilder = MeshBuilder.StartMesh("~" + id.ToString());
				meshBuilder.MergeDuplicatePositions = false;
				
				// Import the vertices and indices of the current mesh definition.
				this.ImportVertices(meshBuilder, meshXml, context);
				this.ImportIndices(meshBuilder, meshXml, context);

				// Optimize the mesh content
				MeshContent meshContent = meshBuilder.FinishMesh();
				MeshHelper.OptimizeForCache(meshContent);
				
				// Add the mesh content to the root content.
				this.rootContent.Children.Add(meshContent);
			}
		}

		private void ImportVertices(MeshBuilder meshBuilder, XmlNode meshXml, ContentImporterContext context)
		{
			// Assert parameters.
			Debug.Assert(meshBuilder != null);
			Debug.Assert(meshXml != null);
			Debug.Assert(context != null);

			// Iterate on all vertex definitions of the current mesh.
			this.vertexXmlElementsById.Clear();
			foreach (XmlElement vertexXml in meshXml.SelectNodes(".//vertex"))
			{
				// Get the id of the vertex.
				int id = XmlConvert.ToInt32(vertexXml.Attributes["id"].Value);

				// Get the position of the vertex.
				Vector3 position;
				position.X = XmlConvert.ToSingle(vertexXml.Attributes["x"].Value);
				position.Y = XmlConvert.ToSingle(vertexXml.Attributes["y"].Value);
				position.Z = XmlConvert.ToSingle(vertexXml.Attributes["z"].Value);

				// Create the vertex and store the index in the mapping collection.
				int index = meshBuilder.CreatePosition(position);
				this.indices.Add(id, index);

				/// Index the vertex xml by id.  Used later.
				this.vertexXmlElementsById.Add(id, vertexXml);
			}
		}

		private void ImportIndices(MeshBuilder meshBuilder, XmlNode meshXml, ContentImporterContext context)
		{
			// Assert parameters.
			Debug.Assert(meshBuilder != null);
			Debug.Assert(meshXml != null);
			Debug.Assert(context != null);

			// Hold onto material id.
			int materialId = XmlConvert.ToInt32(meshXml.Attributes["materialId"].Value);

			// Define vertex channels.
			int normalChannelIndex = meshBuilder.CreateVertexChannel<Vector3>(VertexChannelNames.Normal(0));
			int colorChannelIndex = meshBuilder.CreateVertexChannel<Color>(VertexChannelNames.Color(0));
			int textureCoordinatesIndex = meshBuilder.CreateVertexChannel<Vector2>(VertexChannelNames.TextureCoordinate(0));

			// Iterate on all triangle definitions of the current mesh.
			Dictionary<int, Vector3> vertexNormals = new Dictionary<int, Vector3>();
			Dictionary<int, Vector2> vertexTextureCoordinates = new Dictionary<int, Vector2>();

			foreach (XmlNode triangleXml in meshXml.SelectNodes(".//triangle"))
			{
				// Iterate 3 times for each vertex of the current triangle.
				for (int triangleIndex = 0; triangleIndex <= 2; triangleIndex++)
				{
					// Get the current vertex id.
					int vertexId = XmlConvert.ToInt32(triangleXml.Attributes[String.Format("vertexId{0}", triangleIndex)].Value);

					// Get the vertex from the id.
					XmlNode vertexXml = this.vertexXmlElementsById[vertexId];

					// Get the normal of the vertex.
					Vector3 normal;
					normal.X = XmlConvert.ToSingle(vertexXml.Attributes["normX"].Value);
					normal.Y = XmlConvert.ToSingle(vertexXml.Attributes["normY"].Value);
					normal.Z = XmlConvert.ToSingle(vertexXml.Attributes["normZ"].Value);

					// Get the texture coordinates.
					Vector2 textureCoordinates;
					textureCoordinates.X = XmlConvert.ToSingle(vertexXml.Attributes["u"].Value);
					textureCoordinates.Y = XmlConvert.ToSingle(vertexXml.Attributes["v"].Value);
					//textureCoordinates.X = XmlConvert.ToSingle(triangleXml.Attributes[String.Format("u{0}", triangleIndex)].Value);
					//textureCoordinates.Y = XmlConvert.ToSingle(triangleXml.Attributes[String.Format("v{0}", triangleIndex)].Value);
										
					// Apply mesh vertex attributes.
					meshBuilder.SetVertexChannelData(normalChannelIndex, normal);
					meshBuilder.SetVertexChannelData(colorChannelIndex, new Color(((BasicMaterialContent)this.materials[materialId]).DiffuseColor.Value));
					meshBuilder.SetVertexChannelData(textureCoordinatesIndex, textureCoordinates);
					
					meshBuilder.SetMaterial(this.materials[materialId]);				

					// Add the vertex index to the index buffer.
					meshBuilder.AddTriangleVertex(this.indices[vertexId]);
				}
			}
		}
	}
}
